2. Instruction Set Architecture

2指令集架构
AP SHANTHI博士

 

本模块的目标是了解指令集架构的重要性,讨论设计机器指令集架构时需要考虑的特性,并查看示例 ISA、MIPS。

 

我们已经看到计算机体系结构课程由两个部分组成——指令集体系结构和计算机组织本身。ISA 指定了处理器能够做什么和 ISA,它是如何完成的。所以指令集架构基本上是你的硬件和软件之间的接口。您可以与硬件交互的唯一方式是处理器的指令集。要指挥计算机,你需要说它的语言,指令是计算机语言的词,指令集基本上是它的词汇。除非您了解词汇并且词汇量非常好,否则您无法从机器中获得良好的收益。ISA 是机器的一部分,对汇编语言程序员或编译器编写者或应用程序程序员可见。它是您拥有的唯一接口,因为指令集架构是计算机可以做什么的规范,而机器必须以这样一种方式制造,即它可以执行您的 ISA 中指定的任何内容。您可以与您的机器对话的唯一方法是通过 ISA。这让您了解硬件和软件之间的接口。您可以与您的机器对话的唯一方法是通过 ISA。这让您了解硬件和软件之间的接口。您可以与您的机器对话的唯一方法是通过 ISA。这让您了解硬件和软件之间的接口。

 

让我们假设您有一个用 C 编写的高级程序,它独立于您要工作的体系结构。这个高级程序必须翻译成特定于特定体系结构的汇编语言程序。假设您发现这包含许多指令,例如 LOAD、STORE、ADD 等,其中,您用高级语言编写的任何内容现在都已翻译成一组特定于具体架构。此处显示的所有这些指令都是 MIPS 架构的指令集架构的一部分。这些都是英文的,这对处理器来说是无法理解的,因为处理器毕竟是由只能理解零和一的数字组件组成的。因此,这种汇编语言必须被精细地翻译成机器语言,即由零和一组成的目标代码。因此,从高级语言到汇编语言和二进制代码的翻译必须由编译器和汇编器完成。

 

我们将查看指令集的特性,看看什么会进入零和一,以及如何将零和一解释为数据、指令或地址。设计的 ISA 应该经过多次实现,应该具有可移植性,应该具有兼容性,应该以多种不同的方式使用,因此应该具有通用性,还应该为其他级别提供方便的功能 。下面给出了 ISA 的分类。

 

分类

 

ISA 根据处理器中的内部存储而有所不同。因此,根据操作数的存储位置以及它们是显式还是隐式命名,可以将 ISA 分类如下:

 

单累加器组织,将通用寄存器之一命名为累加器,并使用它来存储操作数之一。这表明其中一个操作数隐含在累加器中,如果另一个操作数与指令一起指定就足够了。
通用寄存器组织,明确指定所有操作数。根据操作数是否在内存或寄存器中可用,它可以进一步分类为
– 寄存器 – 寄存器,其中寄存器用于存储操作数。这种架构实际上也称为加载 - 存储架构,因为只有加载和存储指令才能具有内存操作数。

           – 寄存器 – 内存,其中一个操作数在寄存器中,另一个在内存中。

            – Memory – memory ,其中所有操作数都指定为内存操作数。

栈组织,将操作数放入栈中,操作在栈顶进行。此处隐式指定操作数。
 

让我们假设您必须执行操作 A = B + C,其中所有三个操作数都是内存操作数。在基于累加器的 ISA 的情况下,我们假设通用寄存器之一被指定为累加器并且其中一个操作数将始终在累加器中可用,您必须首先将一个操作数加载到累加器中而 ADD 指令只会指定操作数的地址。在基于 GPR 的 ISA 中,您有三种不同的分类。在寄存器内存 ISA 中,一个操作数必须移动到任何寄存器中,另一个可以是内存操作数。在寄存器 – 寄存器 ISA 中,两个操作数都必须移动到两个寄存器中,并且 ADD 指令仅适用于寄存器。内存 – 内存 ISA 允许两个内存操作数。所以可以直接添加。在基于堆栈的 ISA 中,您必须首先将两个操作数压入堆栈,然后简单地给出一条 add 指令,该指令将添加堆栈的顶部两个元素,然后将结果存储在堆栈中。因此,您可以从这些示例中看到执行相同操作的不同方式,这显然取决于 ISA。在所有这些 ISA 中,寄存器 – 寄存器 ISA 非常流行并用于所有 RISC 架构。

 

我们现在将看看在设计指令集架构时需要考虑哪些不同的特性。他们是:

指令类型(指令集中的操作)
操作数的类型和大小
寻址模式
寻址内存
编码和指令格式
编译器相关问题
 

首先,您必须决定指令的类型,即您希望在 ISA 中支持的各种指令是什么。计算机程序执行的任务由一系列小步骤组成,例如将两个数字相乘、将数据从寄存器移动到内存位置、测试特定条件(如零)、从输入设备读取字符或发送要显示到输出设备等的字符。计算机必须具有以下类型的指令:

数据传输说明
数据操作说明
程序排序和控制指令
输入输出指令
 

数据传输指令执行计算机系统中各个存储位置之间的数据传输,即。寄存器、内存和 I/O。由于指令和数据都存储在内存中,因此处理器需要从内存中读取指令和数据。处理后的结果必须存储在内存中。因此,需要两个涉及内存的基本操作,即Load(或Read或Fetch)和Store(或Write))。Load 操作将数据的副本从内存传输到处理器,Store 操作将数据从处理器移动到内存。需要其他数据传输指令将数据从一个寄存器传输到另一个寄存器或从/向 I/O 设备和处理器传输数据。

 

数据操作指令对数据执行操作并指示处理器的计算能力。这些运算可以是算术运算、逻辑运算或移位运算。算术运算包括加法(有和没有进位)、减法(有和没有借位)、乘法、除法、自增、自减和求一个数的补码。逻辑和位操作指令包括AND、OR、XOR、清除进位、设置进位等。同样,您可以执行不同类型的移位和循环操作。

 

我们通常假设指令的顺序流。也就是说,存储在后续位置的指令一个接一个地执行。但是,您可以使用程序排序和控制指令来帮助您更改程序流程。这最好用一个例子来解释。考虑添加一个包含n 个数字的列表的任务。下面给出了一个可能的顺序。

 

移动 DATA1, R0

添加数据2,R0

添加DATA3,R0

添加DATAn,R0

移动 R0, SUM

 

包含n 个数字的内存位置的地址符号表示为 DATA1, DATA2, ... . 、DATAn 和单独的 Add 指令用于将每个 Databer 添加到寄存器 R0 的内容中。将所有数字相加后,结果将放置在内存位置 SUM 中。可以在程序循环中放置单个 Add 指令,而不是使用一长串 Add 指令,如下所示:

 

移动 N,R1

清除 R0

LOOP 确定“Next”号码的地址并将“Next”号码添加到R0

递减 R1

分支 > 0, LOOP

移动 R0, SUM

 

循环是根据需要执行多次的直线指令序列。它从位置 LOOP 开始,到指令 Branch >结束0. 在每次通过此循环时,确定下一个列表条目的地址,并提取该条目并将其添加到 R0。操作数的地址可以通过多种方式指定,如下一节将介绍。现在,您需要知道如何创建和控制程序循环。假设列表中的条目数 n 存储在内存位置 N 中。寄存器 R1 用作计数器来确定循环执行的次数。因此,位置 N 的内容在程序开始时被加载到寄存器 R1 中。然后,在循环体中,指令 Decrement R1 每次通过循环将 R1 的内容减少 1。只要递减运算的结果大于零,循环就会重复执行。

 

您现在应该能够理解分支指令。这种类型的指令将一个新值加载到程序计数器中。结果,处理器在这个新地址(称为分支目标)处获取并执行指令,而不是在按顺序地址顺序跟随分支指令的位置处的指令。分支指令可以是有条件的或无条件的。一个无条件分支指令不分支到指定的地址而不管任何病症。一个条件分支仅当满足指定条件时,指令才会导致分支。如果不满足条件,则PC按正常方式递增,按地址顺序取下一条指令并执行。在上面的例子中,指令 Branch > 0 LOOP(如果大于 0 则分支)是一条条件分支指令,如果前一条指令的结果(即寄存器 R1 中的递减值)更大,则该指令会导致分支到位置 LOOP比零。这意味着只要列表中有尚未添加到 R0 的条目,就会重复循环。在 n的末尾第一次遍历循环时,递减指令产生的值为零,因此不会发生分支。取而代之的是获取并执行 Move 指令。它将最终结果从 R0 移动到内存位置 SUM。一些 ISA 将此类指令称为Jumps 。处理器会跟踪有关各种操作结果的信息,以供后续条件分支指令使用。这是通过在单个位中记录所需信息来实现的,通常称为条件代码标志。这些标志通常在称为条件代码寄存器或状态寄存器的特殊处理器寄存器中组合在一起. 个别条件代码标志设置为 1 或清除为 0,具体取决于执行的操作的结果。一些常用的标志是:符号、零、溢出和进位。调用和返回指令与子程序结合使用。子程序是执行给定计算任务的自包含指令序列。在程序执行过程中,可能会在主程序的不同位置多次调用子程序以执行其功能。每次调用子程序时,都会执行一个分支到子程序的开头以开始执行其指令集。执行完子程序后,通过返回指令分支返回到主程序。中断也可以改变程序的流程。程序中断是指由于外部或内部生成的请求而将程序控制从当前运行的程序转移到另一个服务程序。服务程序执行完毕后,控制权返回到原来的程序。中断程序原则上与子程序调用非常相似,除了三种变化:(1)中断通常由内部或外部信号发起,除了指令的执行(2)中断服务程序的地址由硬件或从中断信号或引起中断的指令中的某些信息确定;(3) 中断程序通常存储定义 CPU 状态所需的所有信息,而不是仅存储程序计数器。因此,当处理器中断时,它保存处理器的当前状态,包括返回地址、寄存器内容和称为处理器状态字(PSW)的状态信息,然后跳转到中断处理程序或中断服务程序。完成此操作后,它返回到主程序。中断在下一个输入/输出单元中详细处理。

 

输入和输出指令用于在寄存器、存储器和输入/输出设备之间传输信息。可以使用专门执行 I/O 传输的特殊指令,或使用内存相关指令本身来进行 I/O 传输。

 

假设您正在设计一个旨在执行特定应用程序的嵌入式处理器,那么您肯定必须携带特定于该特定应用程序的指令。当您设计通用处理器时,您只需考虑包括所有通用类型的指令。专用指令的示例可能是与媒体和信号处理相关的指令,例如尝试利用数据级并行性的向量类型指令,其中将对不同的数据执行相同的加法或减法运算,然后您 可能需要查看在饱和算术运算中,乘法和累加器指令。

 

数据类型和大小指示处理器支持的各种数据类型及其长度。常见操作数类型 – 字符(8 位)、半字(16 位)、字(32 位)、单精度浮点(1 个字)、双精度浮点(2 个字)、整数 – 二进制补码二进制数,通常为字符在 ASCII 中,遵循 IEEE 标准 754 的浮点数以及打包和未打包的十进制数。

 

 

寻址模式

 

指令的操作字段指定要执行的操作。此操作必须对直接提供或存储在计算机寄存器或内存字中的某些数据执行。在程序执行期间选择操作数的方式取决于指令的寻址模式。寻址模式指定了在实际引用操作数之前解释或修改指令地址字段的规则。在本节中,您将了解现代处理器中最重要的寻址模式。

 

计算机使用寻址模式技术来适应以下一种或两种情况:

 

1. 通过提供诸如内存指针、循环控制计数器、数据索引和程序重定位等工具,为用户提供编程的多功能性。

 

2. 减少指令寻址域的位数。

 

当您用高级语言编写程序时,您会使用常量、局部和全局变量、指针和数组。将高级语言程序翻译成汇编语言时,编译器必须能够使用运行程序的计算机指令集中提供的工具来实现这些结构。在指令中指定操作数位置的不同方式称为寻址模式。变量和常量是最简单的数据类型,几乎可以在每个计算机程序中找到。在汇编语言中,变量是通过分配一个寄存器或一个内存位置来保存它的值来表示的。

 

寄存器模式——操作数是处理器寄存器的内容;寄存器的名称(地址)在指令中给出。

 

绝对模式——操作数位于内存位置;该位置的地址在指令中明确给出。这也称为Direct。

 

地址和数据常量可以使用立即模式以汇编语言表示。

 

立即模式——操作数在指令中明确给出。例如,指令立即移动 200 ,R0 将值 200 放入寄存器 R0。显然,立即模式仅用于指定源操作数的值。一个常见的约定是在值前面使用尖号 (#) 来指示该值将用作立即数操作数。因此,我们以 Move #200, R0 的形式编写上述指令。常量值在高级语言程序中经常使用。例如,语句 A = B + 6 包含常量 6。假设 A 和 B 之前已经声明为变量并且可以使用绝对模式访问,则该语句可以编译如下:

    移动 B,R1

添加#6,R1

移动 R1, A

 

常量还用于汇编语言中以增加计数器、测试某些位模式等。

 

间接模式——在随后的寻址模式中,指令不明确给出操作数或其地址。相反,它提供可以确定操作数的内存地址的信息。我们称这个地址为有效 地址(EA) 操作数。在这种模式下,操作数的有效地址是其地址出现在指令中的寄存器或内存位置的内容。我们通过将寄存器的名称或指令中给出的内存地址放在括号中来表示间接。例如,考虑指令 Add (R1), R0。为了执行 Add 指令,处理器使用寄存器 R1 中的值作为操作数的有效地址。它向内存请求读取操作以读取该位置的内容。读取的值是所需的操作数,处理器将其添加到寄存器 R0 的内容中。如指令 Add (A), R0 中所示,也可以通过存储器位置进行间接寻址。在这种情况下,处理器首先读取内存位置 A 的内容,然后使用该值作为地址请求第二次读取操作以获取操作数。包含操作数地址的寄存器或内存位置称为指针。间接和指针的使用是编程中重要而强大的概念。更改示例中位置 A 的内容会获取不同的操作数以添加到寄存器 R0。

 

索引模式——你学习的下一个寻址模式为访问操作数提供了一种不同的灵活性。它在处理列表和数组时很有用。在这种模式下,操作数的有效地址是通过将一个常量值(位移)添加到寄存器的内容中来生成的。所使用的寄存器可以是为此目的而提供的专用寄存器,也可以是处理器中的任何一种通用寄存器。在任何一种情况下,它都被称为索引寄存器. 我们将索引模式象征性地表示为 X(Ri),其中 X 表示指令中包含的常量值,Ri 是所涉及的寄存器的名称。操作数的有效地址由 EA = X + [Ri] 给出。变址寄存器的内容在生成有效地址的过程中不会发生变化。在汇编语言程序中,常量 X 可以作为显式数字或表示数值的符号名称给出。当指令被翻译成机器码时, 常量 X 作为指令的一部分给出,通常用比计算机字长少的位来表示。由于 X 是有符号整数,因此在添加到寄存器的内容之前必须将其符号扩展到寄存器长度。

 

相对模式——上面的讨论使用通用处理器寄存器定义了索引模式。如果使用程序计数器 PC 而不是通用寄存器,则可以获得此模式的有用版本。然后,X (PC) 可用于寻址与程序计数器当前指向的位置相距 X 字节的存储器位置。由于寻址位置与程序计数器“相对”,程序计数器始终标识程序中的当前执行点,因此名称“相对”模式与这种类型的寻址相关联。在这种情况下,有效地址由使用程序计数器代替通用寄存器 Ri 的索引模式确定。这种寻址方式通常与控制流指令一起使用。

 

虽然这种模式可用于访问数据操作数。但是,它最常见的用途是在分支指令中指定目标地址。我们之前讨论过的诸如 Branch > 0 LOOP 之类的指令会导致程序执行转到由名称 LOOP 标识的分支目标位置,如果满足分支条件。可以通过将其指定为相对于程序计数器当前值的偏移量来计算该位置。由于分支目标可能在分支指令之前或之后,因此偏移量以有符号数的形式给出。回想一下,在执行一条指令期间,处理器递增 PC 以指向下一条指令。大多数计算机在相对模式下计算有效地址时使用此更新值。

 

下面描述的两种模式对于访问存储器中连续位置中的数据项很有用。

 

自动递增模式——操作数的有效地址是指令中指定的寄存器的内容。访问操作数后,该寄存器的内容会自动递增以指向列表中的下一项。我们通过将指定的寄存器放在括号中来表示自动递增模式,以表示该寄存器的内容作为有效地址,后跟一个加号表示这些内容在操作数被访问后要递增。因此,自动增量模式写为(Ri )+。

 

Autodecrement 模式——作为Autoincrement 模式的伴侣,另一种有用的模式以相反的顺序访问列表中的项目。在自动递减模式下,指令中指定的寄存器内容首先自动递减,然后作为操作数的有效地址。我们通过将指定的寄存器放在括号中来表示 Autodecrement 模式,前面有一个减号,表示寄存器的内容在用作有效地址之前要递减。因此,我们写- (Ri)。在这种模式下,操作数按地址降序访问。您可能想知道为什么地址在 Autodecrement 模式下使用前会递减,而在 Autoincrement 模式下使用后会增加。这样做的主要原因是这两种模式可以一起使用来实现一个堆栈。

 

指令格式

 

前面的部分已经向您展示了处理器可以执行不同类型的指令,并且有不同的方式来指定操作数。一旦决定了所有这些,这些信息就必须以指令格式的形式呈现给处理器。指令中的位数被分成称为字段的组。指令格式中最常见的字段是

 

1. 操作码字段,指定要执行的操作。位数将指示可以执行的操作数。

 

2. 指定内存地址或处理器寄存器的地址字段。位数取决于内存的大小或寄存器的数量。

 

3. 模式字段,指定确定操作数或有效地址的方式。这取决于处理器支持的寻址模式的数量。

 

地址字段的数量可以是三个、两个或一个,具体取决于所使用的 ISA 类型。另请注意,根据支持的操作数数量和各个字段的大小,指令的长度会有所不同。一些处理器将所有指令装入单一大小的格式,而其他处理器则使用不同大小的格式。因此,您有固定格式或可变格式。

 

解释内存地址——你基本上有两种类型的内存地址解释——大端排列和小端排列。内存通常按字节排列,内存位置的唯一地址能够存储 8 位信息。但是当你看处理器的字长时,处理器的字长可能会超过一个字节。假设您查看一个 32 位处理器,它由四个字节组成。这四个字节跨越四个内存位置。当您指定一个字的地址时,您将如何指定该字的地址——您是将最高有效字节的地址指定为字的地址(大端)还是指定最低有效字节的地址( little end)作为词的地址。这区分了大端排列和小端排列。IBM、摩托罗拉、惠普遵循大端排列,英特尔遵循小端排列。此外,当数据跨越不同的内存位置时,如果您尝试访问与字边界对齐的字,我们说存在对齐。如果您尝试访问不在单词边界处开始的单词,您仍然可以访问,但它们没有对齐。是否支持访问未对齐的数据是一个设计问题。即使您被允许访问未对齐的数据,通常也需要更多的内存周期来访问数据。如果您尝试访问与单词边界对齐的单词,我们就说存在对齐。如果您尝试访问不在单词边界处开始的单词,您仍然可以访问,但它们没有对齐。是否支持访问未对齐的数据是一个设计问题。即使您被允许访问未对齐的数据,通常也需要更多的内存周期来访问数据。如果您尝试访问与单词边界对齐的单词,我们就说存在对齐。如果您尝试访问不在单词边界处开始的单词,您仍然可以访问,但它们没有对齐。是否支持访问未对齐的数据是一个设计问题。即使您被允许访问未对齐的数据,通常也需要更多的内存周期来访问数据。

 

最后看看编译器的作用,当您定义指令集架构时,编译器可以发挥很多作用。人们认为编译器和架构将相互独立的日子已经一去不复返了 。只有当编译器知道处理器的内部架构时,它才能生成优化的代码。因此,体系结构必须将自身暴露给编译器,而编译器将必须利用任何暴露的硬件。ISA 应该是编译器友好的。ISA 可以帮助编译器的基本方法是规律性、正交性和权衡不同选项的能力。

最后,关于 80×86 和 MIPS 讨论了 ISA 的所有特性。

 

1.  ISA 的类别——当今几乎所有的 ISA 都被归类为通用寄存器架构,其中的操作数要么是寄存器,要么是内存位置。80×86有16个通用寄存器和16个可以保存浮点数据的寄存器,而MIPS有32个通用寄存器和32个浮点寄存器。此类的两个流行版本是寄存器内存ISA,例如 80×86,它可以作为许多指令的一部分访问内存,以及加载存储ISA,例如 MIPS,它只能通过加载或存储指令访问内存。所有最近的 ISA 都是加载存储。

 

2. 内存寻址——几乎所有台式机和服务器计算机,包括 80×86 和 MIPS,都使用字节寻址来访问内存操作数。某些体系结构(如 MIPS)要求对象必须对齐。如果A mod s = 0,则对字节地址A处大小为s字节的对象的访问是对齐的。80×86 不需要对齐,但如果操作数对齐,则访问通常会更快。

 

3. 寻址模式——除了指定寄存器和常量操作数之外,寻址模式还指定了内存对象的地址。MIPS 寻址模式是寄存器、立即(对于常量)和位移,其中将常量偏移量添加到寄存器以形成内存地址。80×86 支持位移的三加三变体:无寄存器(绝对)、两个寄存器(基于位移索引)、两个寄存器,其中一个寄存器乘以操作数的字节大小(基于缩放索引和位移) )。它更像最后三个,减去位移字段:寄存器间接、索引和基于缩放索引。

 

4. 操作数的类型和大小——与大多数 ISA 一样,MIPS 和 80×86 支持 8 位(ASCII 字符)、16 位(Unicode 字符或半字)、32 位(整数或字)、64 的操作数大小-bit(双字或长整数),以及 32 位(单精度)和 64 位(双精度)中的 IEEE 754 浮点。80×86 还支持 80 位浮点(扩展双精度)。

 

5. 运算——一般的运算类别是数据传输、算术逻辑、控制和浮点。MIPS 是一种简单且易于流水线化的指令集架构,它是 2006 年使用的 RISC 架构的代表。80×86 具有更丰富和更大的操作集。

 

6. 控制流指令——几乎所有的 ISA,包括 80×86 和 MIPS,都支持条件分支、无条件跳转、过程调用和返回。两者都使用 PC 相对寻址,其中分支地址由添加到 PC 的地址字段指定。有一些小的区别。 MIPS 条件分支(BE、BNE 等)测试寄存器的内容,而 80×86 分支 (JE、JNE 等)测试条件代码位设置为算术/逻辑运算的副作用. MIPS 过程调用 (JAL) 将返回地址放置在寄存器中,而 80×86 调用 (CALLF) 将返回地址放置在内存中的堆栈中。

编码 ISA —编码有两种基本选择:固定长度和可变长度。所有 MIPS 指令都是 32 位长,这简化了指令解码(如下所示)。80×86 编码是可变长度的,范围从 1 到 18 个字节。变长指令比定长指令占用更少的空间,因此为 80×86 编译的程序通常比为 MIPS 编译的相同程序小。请注意,上述选择将影响指令如何编码为二进制表示。例如,寄存器的数量和寻址方式的数量对指令的大小都有很大的影响,因为寄存器字段和寻址方式字段可以在一条指令中出现多次。
 

总而言之,我们已经研究了 ISA 的分类以及在设计 ISA 时需要确定的各种特征。我们还查看了示例 ISA、MIPS ISA 和 80×86 ISA。

 

 

网页链接/支持材料

 

http://en.wikipedia.org/wiki/Instruction_set
计算机体系结构 – 定量方法,John L. Hennessy 和 David A. Patterson,第 5 版,Morgan Kaufmann,Elsevier,2011。
计算机组织与设计——硬件/软件接口,David A. Patterson 和 John L. Hennessy,第 4 版,Morgan Kaufmann,Elsevier,2009 年。
计算机组织,Carl Hamacher、Zvonko Vranesic 和 Safwat Zaky,第 5 版,McGraw-Hill 高等教育,2011 年。